1. Instalación de librerías.

 [1] ".GlobalEnv"          "package:cartography" "package:mapview"     "package:caret"       "package:lattice"     "package:tmaptools"  
 [7] "package:formatR"     "package:devtools"    "package:usethis"     "package:tmap"        "package:forcats"     "package:stringr"    
[13] "package:dplyr"       "package:purrr"       "package:readr"       "package:tidyr"       "package:tibble"      "package:tidyverse"  
[19] "package:maptools"    "package:rgeos"       "package:rgdal"       "package:sp"          "package:leaflet"     "package:ggmap"      
[25] "package:ggplot2"     "package:maps"        "tools:rstudio"       "package:stats"       "package:graphics"    "package:grDevices"  
[31] "package:utils"       "package:datasets"    "package:methods"     "Autoloads"           "package:base"       
library(ggmap)
library(rgdal)
library(rgeos)
library(maptools)
library(dplyr)
library(tidyr)
library(tmap)
library(geospatial) # Instalado en ejercicios anteriores 
library(sp)
library(maps)
library(raster)
library(tmaptools)
library(leaflet)
library(cartography)

2. Representación de mapas con el paquete tmap

Aunque hay muchos librerías para la representación de mapas (como ggmap que ya hemos utilizado), hay librerías más específicas que facilitarán muchas tareas. Una de ellas, que mantiene la filosofía de ggplot, es tmap. Esta librería acepta objetos tipo sp, mucho más efiecientes que los data frames.

NOTA: Esta librería ha cambiado a principios de 2019 y los objetos almacenados son de tipo sf, esto afecta al formato de los datos. Si procede se pueden cambiar a formato sp con la función as(…, ‘Spatial’).

Referencia sf (simple features) objects have a simpler structure than sp objects. An sf object is nothing more than a data.frame with a special geometry column that contains the geometries for the corresponding rows. Such a geometry can be of type spatial point(s), line(s) or polygon(s) or any combination of these in a ‘geometrycollection’ (see vignette(“sf1”)). The layers functions, such as tm_polygons, will only draw what they are supposed to draw (in this case polygons). The newly added layer function tm_sf will draw all geometries

Primeros pasos con tmap:. Mapas usando tmap

Información completa en el libro gratuito: Geocomputation with R

Usaremos un formato de representación por capas, similar a ggmap y a ggplot:

Tipos de presentaciones para documentos html

tmap_mode(“plot”): para visualación estática tmap_mode(“view”): para visualización interactiva. Permite activar pop-up

La opción seleccionada se mantiene activa hasta que se indique lo contrario.

Ejemplo de mapas en modo plot:

tmap_mode("plot")
data(World)
tm_shape(World) +
  tm_polygons("HPI") # Happy Planet Index

Ejemplo de mapas en modo view:

tmap_mode("view")
data(World)
#World<-as(World,'Spatial') # No necesario, acepta objetos sp y sf
tm_shape(World) +
  tm_polygons("HPI", id = "iso_a3", popup.vars = TRUE)
NA

Ejemplo. Gráfico básico. Quick Thematic Map (qtm)

Los datos utilizados están disponibles en la librería tmap y también en http://www.naturalearthdata.com/

NOTA: En la última versón de tmaplos ficheros vienen en formato sf en lugar de sp. Después de la carga los trasformaremos a clase spcon la función as por ejemplo World<-as(World, ‘Spatial’),si bien las funciones de representación del paquete tmap acepta ambos formatos.


library(tmap)

data(World, rivers, metro)

str(World)
Classes ‘sf’ and 'data.frame':  177 obs. of  16 variables:
 $ iso_a3      : Factor w/ 177 levels "AFG","AGO","ALB",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ name        : Factor w/ 177 levels "Afghanistan",..: 1 4 2 166 6 7 5 56 8 9 ...
 $ sovereignt  : Factor w/ 171 levels "Afghanistan",..: 1 4 2 159 6 7 5 52 8 9 ...
 $ continent   : Factor w/ 8 levels "Africa","Antarctica",..: 3 1 4 3 8 3 2 7 6 4 ...
 $ area        : Units: [km^2] num  652860 1246700 27400 71252 2736690 ...
 $ pop_est     : num  28400000 12799293 3639453 4798491 40913584 ...
 $ pop_est_dens: num  43.5 10.3 132.8 67.3 15 ...
 $ economy     : Factor w/ 7 levels "1. Developed region: G7",..: 7 7 6 6 5 6 6 6 2 2 ...
 $ income_grp  : Factor w/ 5 levels "1. High income: OECD",..: 5 3 4 2 3 4 2 2 1 1 ...
 $ gdp_cap_est : num  784 8618 5993 38408 14027 ...
 $ life_exp    : num  59.7 NA 77.3 NA 75.9 ...
 $ well_being  : num  3.8 NA 5.5 NA 6.5 4.3 NA NA 7.2 7.4 ...
 $ footprint   : num  0.79 NA 2.21 NA 3.14 2.23 NA NA 9.31 6.06 ...
 $ inequality  : num  0.427 NA 0.165 NA 0.164 ...
 $ HPI         : num  20.2 NA 36.8 NA 35.2 ...
 $ geometry    :sfc_MULTIPOLYGON of length 177; first list element: List of 1
  ..$ :List of 1
  .. ..$ : num [1:69, 1:2] 61.2 62.2 63 63.2 64 ...
  ..- attr(*, "class")= chr [1:3] "XY" "MULTIPOLYGON" "sfg"
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
  ..- attr(*, "names")= chr [1:15] "iso_a3" "name" "sovereignt" "continent" ...
str(rivers)
Classes ‘sf’ and 'data.frame':  1616 obs. of  5 variables:
 $ name     : chr  NA "Ebro" "Ebro" "Ebro" ...
 $ type     : Factor w/ 2 levels "Lake Centerline",..: 2 2 2 2 2 1 1 2 2 2 ...
 $ scalerank: int  5 5 5 5 5 5 5 5 5 5 ...
 $ strokelwd: num  2 1.5 2 2.5 3 2 2.5 1.5 2 2 ...
 $ geometry :sfc_LINESTRING of length 1616; first list element:  'XY' num [1:5, 1:2] -73 -73.1 -73.1 -73.2 -73.2 ...
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA
  ..- attr(*, "names")= chr [1:4] "name" "type" "scalerank" "strokelwd"
str(metro)
Classes ‘sf’ and 'data.frame':  436 obs. of  13 variables:
 $ name     : chr  "Kabul" "Algiers" "Luanda" "Buenos Aires" ...
 $ name_long: chr  "Kabul" "El Djazair  (Algiers)" "Luanda" "Buenos Aires" ...
 $ iso_a3   : chr  "AFG" "DZA" "AGO" "ARG" ...
 $ pop1950  : num  170784 516450 138413 5097612 429249 ...
 $ pop1960  : num  285352 871636 219427 6597634 605309 ...
 $ pop1970  : num  471891 1281127 459225 8104621 809794 ...
 $ pop1980  : num  977824 1621442 771349 9422362 1009521 ...
 $ pop1990  : num  1549320 1797068 1390240 10513284 1200168 ...
 $ pop2000  : num  2401109 2140577 2591388 12406780 1347561 ...
 $ pop2010  : num  3722320 2432023 4508434 14245871 1459268 ...
 $ pop2020  : num  5721697 2835218 6836849 15894307 1562509 ...
 $ pop2030  : num  8279607 3404575 10428756 16956491 1718192 ...
 $ geometry :sfc_POINT of length 436; first list element:  'XY' num  69.2 34.5
 - attr(*, "sf_column")= chr "geometry"
 - attr(*, "agr")= Factor w/ 3 levels "constant","aggregate",..: NA NA NA NA NA NA NA NA NA NA ...
  ..- attr(*, "names")= chr [1:12] "name" "name_long" "iso_a3" "pop1950" ...

Observar la diferencia entre las estructuras de sf y sp.

Ejercicio: Dibuja el mapa de España con qtm(), en modo interactivo.

Choropleth Map.

Observa el código y cómo las opciones incluidas modifican el aspecto del mapa.

# choropleth
qtm(World, fill = "economy", format="World", style="col_blind")
tmap_mode('view')
qtm(World, fill="HPI", fill.n=9, fill.palette="div", fill.auto.palette.mapping=FALSE, 
    fill.title="Happy Planet Index", fill.id="name", format="World", style="gray")

qtm(World, fill="area", fill.n=9, fill.palette="div", fill.auto.palette.mapping=TRUE, 
    fill.title="Area", fill.id="area", format="World", style="gray")
NA

Bubble map

Superponemos dos objetos SP en un mapa. Filosofía análoga a ggplot.

qtm(World, borders = NULL) + 
qtm(metro, symbols.size = "pop2010", 
    symbols.title.size= "Metropolitan Areas", 
    symbols.id= "name",
    format = "World")

Ejemplo Usando la forma general tm_XXX.

Usamos el objeto countries_spdf. En primer lugar averiguamos las variables almacenadas en el data frame.

library(sp)
library(tmap)
library(geospatial)

str(countries_spdf,max.level = 2)
Formal class 'SpatialPolygonsDataFrame' [package "sp"] with 5 slots
  ..@ data       :'data.frame': 177 obs. of  6 variables:
  ..@ polygons   :List of 177
  ..@ plotOrder  : int [1:177] 7 136 28 169 31 23 9 66 84 5 ...
  ..@ bbox       : num [1:2, 1:2] -180 -90 180 83.6
  .. ..- attr(*, "dimnames")=List of 2
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
  ..$ comment: chr "TRUE"
str(countries_spdf@data)
'data.frame':   177 obs. of  6 variables:
 $ name      : chr  "Afghanistan" "Angola" "Albania" "United Arab Emirates" ...
 $ iso_a3    : chr  "AFG" "AGO" "ALB" "ARE" ...
 $ population: num  28400000 12799293 3639453 4798491 40913584 ...
 $ gdp       : num  22270 110300 21810 184300 573900 ...
 $ region    : chr  "Asia" "Africa" "Europe" "Asia" ...
 $ subregion : chr  "Southern Asia" "Middle Africa" "Southern Europe" "Western Asia" ...

La capa tm_shape() es como la función ggplot(). Contiene el objeto sp/sf con los datos y es la base sobre la que se superponen las demás capas. Posteriormente veremos que podemos superponer varias capasa tm_shape() de diferentes objetos sp en un mismo mapa.

La capa tm_fill() hace el relleno de los polígonos de manera similar a tm_polygon(col = “…”). Veamos un ejemplo.

  p<-tm_shape(countries_spdf) 
  p<-p+tm_fill(col = "population", style="quantile") # consulta en qué consiste el estilo "quantile"
  show(p)

Sintaxis alternativa

  tm_shape(countries_spdf) + tm_fill(col = "population",style="quantile") 

Añade una capa tm_borders() con el atributo col = “burlywood”. Observa el efecto.

tm_shape(countries_spdf)+tm_fill(col='population')

Añade una capa tm_bubbles() con size = “population” y col=“green”. Cambia el color de las fronteras a “black”

Otro ejemplo de Choropleth

Aquí representamos dos magnitudes: “well being” en el color de los polígonos, y “area”, proporcional al tamaño del texto.

data(World)
Europe<-World[World$continent=='Europe',]

tmap_mode('plot') # en modo view no funciona el escalado de las etiquetas
tm_shape(Europe) +
    tm_polygons("well_being", textNA="Non-European countries", title="Well-Being Index") +
    tm_text("iso_a3", size="AREA", root=5) + 
tm_format("World") +
tm_style("grey")

NOTA: No está disponible el mapa ‘Europe’ como tal en la última versión de tmap (no podemos hacer data(Europe), sino que debemos restringir ‘World’ por continente.

Ejemplo de Superposición de múltiples objetos sp en un mismo mapa.

Comandos para la representación de mapas con tmap:

Observa el procedimiento para superponer múltiples objetos (SpatialPoints, SpatialLines y SpatialPolygons) en un mismo mapa.

En primer lugar observamos la estructura de cada uno de los objetos

# Cargamos los datos (con conjuntos incluidos en las librerías)
#data(package='tmap')
data(land, rivers, metro)  # land cover, rivers, metropolitan areas
# land es de tipo raster (stars-raster), las otras dos son de tipo sf

# Transformamos SF en SP
rivers<-as(rivers,'Spatial')
metro<-as(metro, 'Spatial')

# Salvo para usar estos códigos no es necesario pasar a tipo sp
str(land,max.level = 2)
List of 4
 $ cover    : Factor[1:1080, 1:540] w/ 20 levels "Broadleaf Evergreen Forest",..: 20 20 20 20 20 20 20 20 20 20 ...
 $ cover_cls: Factor[1:1080, 1:540] w/ 8 levels "Forest","Other natural vegetation",..: 8 8 8 8 8 8 8 8 8 8 ...
 $ trees    : int [1:1080, 1:540] NA NA NA NA NA NA NA NA NA NA ...
 $ elevation: int [1:1080, 1:540] NA NA NA NA NA NA NA NA NA NA ...
 - attr(*, "dimensions")=List of 2
  ..$ x:List of 7
  .. ..- attr(*, "class")= chr "dimension"
  ..$ y:List of 7
  .. ..- attr(*, "class")= chr "dimension"
  ..- attr(*, "raster")=List of 3
  .. ..- attr(*, "class")= chr "stars_raster"
  ..- attr(*, "class")= chr "dimensions"
 - attr(*, "class")= chr "stars"
##str(land@data)
str(rivers,max.level = 2)
Formal class 'SpatialLinesDataFrame' [package "sp"] with 4 slots
  ..@ data       :'data.frame': 1616 obs. of  4 variables:
  ..@ lines      :List of 1616
  ..@ bbox       : num [1:2, 1:2] -165.2 -50.2 176.3 73.3
  .. ..- attr(*, "dimnames")=List of 2
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
str(rivers@data)
'data.frame':   1616 obs. of  4 variables:
 $ name     : chr  NA "Ebro" "Ebro" "Ebro" ...
 $ type     : Factor w/ 2 levels "Lake Centerline",..: 2 2 2 2 2 1 1 2 2 2 ...
 $ scalerank: int  5 5 5 5 5 5 5 5 5 5 ...
 $ strokelwd: num  2 1.5 2 2.5 3 2 2.5 1.5 2 2 ...
str(metro,max.level = 2)
Formal class 'SpatialPointsDataFrame' [package "sp"] with 5 slots
  ..@ data       :'data.frame': 436 obs. of  12 variables:
  ..@ coords.nrs : num(0) 
  ..@ coords     : num [1:436, 1:2] 69.17 3.04 13.23 -58.4 -64.18 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..@ bbox       : num [1:2, 1:2] -123.1 -37.8 174.8 60.2
  .. ..- attr(*, "dimnames")=List of 2
  ..@ proj4string:Formal class 'CRS' [package "sp"] with 1 slot
str(metro@data)
'data.frame':   436 obs. of  12 variables:
 $ name     : chr  "Kabul" "Algiers" "Luanda" "Buenos Aires" ...
 $ name_long: chr  "Kabul" "El Djazair  (Algiers)" "Luanda" "Buenos Aires" ...
 $ iso_a3   : chr  "AFG" "DZA" "AGO" "ARG" ...
 $ pop1950  : num  170784 516450 138413 5097612 429249 ...
 $ pop1960  : num  285352 871636 219427 6597634 605309 ...
 $ pop1970  : num  471891 1281127 459225 8104621 809794 ...
 $ pop1980  : num  977824 1621442 771349 9422362 1009521 ...
 $ pop1990  : num  1549320 1797068 1390240 10513284 1200168 ...
 $ pop2000  : num  2401109 2140577 2591388 12406780 1347561 ...
 $ pop2010  : num  3722320 2432023 4508434 14245871 1459268 ...
 $ pop2020  : num  5721697 2835218 6836849 15894307 1562509 ...
 $ pop2030  : num  8279607 3404575 10428756 16956491 1718192 ...

Representamos todos simultáneamente (luego explicaremos paso a paso)

tmap_mode('plot')
tm_shape(land) + 
    tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) + 
tm_shape(Europe, is.master = TRUE) +
    tm_borders() +
tm_shape(rivers) +
    tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE) +
tm_shape(metro) +
    tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1, 
        size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6), 
        title.size="Metropolitan Population") +
    tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6, 
        bg.color="white", bg.alpha = .75, 
        auto.placement = 1, legend.size.show = FALSE) + 
tm_format("World") +
tm_style("natural")

Paso a paso

# capa de land cover con una capa raster de porcentaje de superficie cubierta por árboles (gradiente de color dado por breaks)
 p<-tm_shape(land) + 
    tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE)
  show(p)

Paso 1

# Añadir capa de datos de Europa y definirlo como capa maestra, mostrar sus fronteras

p<-tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders()   
show(p)

Paso 2

# Añadir capa de ríos, escalados por su caudal
tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)

Paso 3

# Añadir capa de burbujas, de tamaño proporcional a la población metropolitana
tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1, 
        size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6), 
        title.size="Metropolitan Population") 

NA

Paso 4

# Añadir capa de texto de nombres de metrópolis
tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1, 
        size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6), 
        title.size="Metropolitan Population")+ 
 tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6, 
        bg.color="white", bg.alpha = .75, 
        auto.placement = 1, legend.size.show = FALSE)

NA
NA

Paso 5

# Añadir formato: available formats are: "World", "World_wide", "NLD", "NLD_wide" 
tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1, 
        size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6), 
        title.size="Metropolitan Population")+ 
 tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6, 
        bg.color="white", bg.alpha = .75, 
        auto.placement = 1, legend.size.show = FALSE)+ 
tm_format("World") 

Paso 6

# estilo de representación (gama de colores)
tm_shape(land) + 
tm_raster("trees", breaks=seq(0, 100, by=20), legend.show = FALSE) +
tm_shape(Europe, is.master = TRUE) +
tm_borders() +
tm_shape(rivers)+
tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+
tm_shape(metro)+
tm_bubbles("pop2010", "red", border.col = "black", border.lwd=1, 
        size.lim = c(0, 11e6), sizes.legend = c(1e6, 2e6, 4e6, 6e6, 10e6), 
        title.size="Metropolitan Population")+ 
 tm_text("name", size="pop2010", scale=1, root=4, size.lowerbound = .6, 
        bg.color="white", bg.alpha = .75, 
        auto.placement = 1, legend.size.show = FALSE)+ 
tm_format("World") +
tm_style("natural")

NA

Consideraciones sobre el mapa anterior

  • Este mapa tiene 4 grupos de capas, respectivamente los objetos land, Europe, rivers, y metro. El orden de grupo (capa) corresponde al orden en que se dibuja.
  • Los objetos pueden tener diferentes proyecciones y también pueden cubrir diferentes áreas (bounding boxes). Tanto la proyección como el área cubierta se toman por defecto del objeto definido en la primera tm_shape, pero en este caso en la segunda tm_shape ya que es en la que se ha usado is.master=TRUE. Ten en cuenta que todos los objetos tienen elementos fuera de Europa (ver por ejemplo qtm(rivers)).

Se puede además añadir una capa tm_layout() que controla aspectos como título, márgenes, relación de aspecto, etc.

tmap_mode('plot')
tm_shape(rivers)+tm_lines()+tm_layout(main.title ="Ríos",main.title.position = "center")
# Grosor de la línea dependiente de una variable.
tm_shape(rivers)+tm_lines(lwd="strokelwd", scale=5, legend.lwd.show = FALSE)+tm_layout(main.title ="Ríos",main.title.position = "center")
tm_shape(metro)+tm_dots()+tm_layout(main.title ="Metro",main.title.position = "center")

Ejemplos:

Representación de dos mapas simultánemente con asignación de CRS

# Proyección Robinson
tmap_mode('plot')
robin <- "+proj=robin +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs"

m1 <- tm_shape(World, projection = robin) +
  tm_polygons(c("HPI", "gdp_cap_est"),
              palette = list("RdYlGn", "Purples"),
              style = c("pretty", "fixed"), n = 7, 
              breaks = list(NULL, c(0, 500, 2000, 5000, 10000, 25000, 50000, Inf)),
              title = c("Happy Planet Index", "GDP per capita")) +
  tm_style("natural", earth.boundary = c(-180, -87, 180, 87))  +
  tm_format("World", inner.margins = 0.02, frame = FALSE) +
  tm_legend(position = c("left", "bottom"), bg.color = "gray95", frame = TRUE) +
  tm_credits(c("", "Robinson projection"), position = c("RIGHT", "BOTTOM"))
m1

Visualización de tres magnitudes en el mismo mapa

tmap_mode('plot')
metro$growth <- (metro$pop2020 - metro$pop2010) / (metro$pop2010 * 10) * 100

m2 <- tm_shape(World) +
    tm_polygons("income_grp", palette = "-Blues", 
      title = "Income class", contrast = 0.7, border.col = "grey30", id = "name") +
    tm_text("iso_a3", size = "AREA", col = "grey30", root = 3) +
  tm_shape(metro) +
    tm_bubbles("pop2010", col = "growth", border.col = "black",
      border.alpha = 0.5,
      breaks = c(-Inf, 0, 2, 4, 6, Inf) ,
      palette = "-RdYlGn",
      title.size = "Metro population (2010)", 
      title.col = "Annual growth rate (%)",
      id = "name",
      popup.vars = c("pop2010", "pop2020", "growth")) + 
  tm_style("gray") +
  tm_format("World", frame.lwd = 2)
m2

Fijar un “marker” mediante coordenadas geográficas a partir de dirección.

# obtain geocode address information
etse <- geocode_OSM('ETSE, Burjassot, Spain',  as.sf = TRUE)

# change to interactive mode
tmap_mode("view")
  tm_shape(etse) +
    tm_markers(text="query")

Guardar un mapa: tmap_save()

Permite grabar mapas estáticos y tambien INTERACTIVOS.

library(sp)
library(tmap)
library(geospatial)

tmap_mode('plot')
tm_shape(countries_spdf) +
  tm_grid(n.x = 11, n.y = 11, projection = "+proj=longlat") +
  tm_fill(col = "population", style = "quantile",alpha = 0.2)  +
  tm_borders(col = "burlywood4")

# Guardar un mapa ESTÁTICO
tmap_save(filename="population.png")

# Save un mapa INTERACTIVO
tmap_mode('view')
tm_shape(countries_spdf) +
  tm_grid(n.x = 11, n.y = 11, projection = "+proj=longlat") +
  tm_fill(col = "population", style = "quantile",alpha = 0.2)  +
  tm_borders(col = "burlywood4")
tmap_save(filename="population.html")

# La opción por defecto, cuando se utiliza tmap es que el mapa sea interactivo.
  

Integración en shiny

Podemos incrustar mapas de tmap en shiny con la función tmapOutput() en la parte de UI y renderTmap() en el server:

library(shiny)

ui <- fluidPage(
  tmapOutput("my_tmap")
)

server <- function(input,output) {
  output$my_tmap = renderTmap({
    tm_shape(World, projection="+proj=robin") + tm_polygons("HPI", legend.title = "Happy Planet Index") + tm_style('cobalt')
  })
}

shinyApp(ui, server)

Ejercicio

  1. Dibuja, en modo plot, el mapa del mundo (countries_spdf), de la librería geospatial (usa tm_XXX).

  1. Dibuja el mapa del mundo (countries_spdf) y colorea los paises según el continente.

  1. Añade las iniciales de los paises (iso_a3) con tm_text (usa size=1).

  1. Añade las iniciales de los paises (iso_a3) con un tamaño que sea proporcional al área del país (mira ayuda de tm_text). Repite pero con el texto proporcional a la población.
tm_shape(countries_spdf)+tm_fill(col='region')+tm_text('iso_a3',size='AREA')


tm_shape(countries_spdf)+tm_fill(col='region')+tm_text('iso_a3',size='population')

  1. Dibuja los paises y coloréalos según la DENSIDAD de POBLACIÓN (habitantes/km2). La función area() del paquete raster permite calcular el área de cada polígono de un objeto SpatialPolygonsDataFrame, en metros cuadrados. Prueba el efecto de la opción style=‘quantile’ en tm_fill(), sobre la densidad.
library(tmap)
library(raster)

# Calcular el área de cada país en km^2
countries_spdf$area_km2 <- area(countries_spdf)/10^6

# Calcular la densidad de población
countries_spdf$density <- countries_spdf$population/countries_spdf$area_km2

# Crear un mapa con la densidad de población como variable de color
tm_shape(countries_spdf) +
  tm_fill(col = "density", style = "quantile") +
  tm_text("iso_a3", size = "area_km2") +
  tm_layout(frame = FALSE)

  1. Repite el mapa anterior pero dibuja solo Europa, sin “Russia”.

Europe_wo_russia <- countries_spdf[countries_spdf$name!='Russia' & countries_spdf$region=='Europe',]

# Calcular el área de cada país en km^2
Europe_wo_russia$area_km2 <- area(Europe_wo_russia)/10^6

# Calcular la densidad de población
Europe_wo_russia$density <- Europe_wo_russia$population/Europe_wo_russia$area_km2

# Crear un mapa con la densidad de población como variable de color
tm_shape(Europe_wo_russia) +
  tm_fill(col = "density") +
  tm_text("iso_a3", size = "area_km2") +
  tm_layout(frame = FALSE)

  1. Observa el efecto del parámetro “style” en tm_fill. Prueba style=“quantile”
tm_shape(Europe_wo_russia) +
  tm_fill(col = "density", style = "quantile") +
  tm_text("iso_a3", size = "area_km2") +
  tm_layout(frame = FALSE)

  1. Guarda el mapa en modo estático.
tmap_save(filename = 'Europa_sin_Rusia.png')
LS0tDQp0aXRsZTogJ01hc3RlciBlbiBDaWVuY2lhIGRlIERhdG9zOiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyBlc3BhY2lhbGVzJw0Kc3VidGl0bGU6ICdQYXJ0ZSAyOiBWaXN1YWxpemFjaW9uZXMgZGUgbWFwYXMgY29uICoqdG1hcCoqJw0KYXV0aG9yOiAiRmVybmFuZG8gTWF0ZW8iDQpkYXRlOiAgImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNCcNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNCcNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyMgR2xvYmFsIGNvZGUgb3B0aW9ucw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwNCgkgICAgICAgICAgICAgY2FjaGU9VFJVRSwNCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwNCiAgICAgICAgICAgICAgIHRpZHk9VFJVRSwNCiAgICAgICAgICAgICAgIGNvbW1lbnQ9TkEsDQogICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLA0KICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSkNCg0KYGBgDQoNCiMjIDEuIEluc3RhbGFjacOzbiBkZSBsaWJyZXLDrWFzLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgRXNwZWNpZmljYW1vcyBsYXMgbGlicmVyw61hcyBuZWNlc2FyaWFzIGVuIGVzdGEgbGlzdGENCg0KcGFja2FnZXMgPSBjKCJtYXBzIiwiZ2dtYXAiLCJsZWFmbGV0IiwicmdkYWwiLCJyZ2VvcyIsIm1hcHRvb2xzIiwidGlkeXZlcnNlIiwidG1hcCIsImRldnRvb2xzIiwiZm9ybWF0UiIsInRtYXB0b29scyIsImNhcmV0IiwnbWFwdmlldycsJ2NhcnRvZ3JhcGh5JykNCg0KDQojdXNlIHRoaXMgZnVuY3Rpb24gdG8gY2hlY2sgaWYgZWFjaCBwYWNrYWdlIGlzIG9uIHRoZSBsb2NhbCBtYWNoaW5lDQojaWYgYSBwYWNrYWdlIGlzIGluc3RhbGxlZCwgaXQgd2lsbCBiZSBsb2FkZWQNCiNpZiBhbnkgYXJlIG5vdCwgdGhlIG1pc3NpbmcgcGFja2FnZShzKSB3aWxsIGJlIGluc3RhbGxlZCBhbmQgbG9hZGVkDQpwYWNrYWdlLmNoZWNrIDwtIGxhcHBseShwYWNrYWdlcywgRlVOID0gZnVuY3Rpb24oeCkgew0KICBpZiAoIXJlcXVpcmUoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoeCwgZGVwZW5kZW5jaWVzID0gVFJVRSxyZXBvcz0naHR0cDovL2NyYW4ucmVkaXJpcy5lcycpDQogICAgbGlicmFyeSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQogIH0NCn0pDQoNCiN2ZXJpZnkgdGhleSBhcmUgbG9hZGVkDQpzZWFyY2goKQ0KDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdnbWFwKQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkocmdlb3MpDQpsaWJyYXJ5KG1hcHRvb2xzKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KGdlb3NwYXRpYWwpICMgSW5zdGFsYWRvIGVuIGVqZXJjaWNpb3MgYW50ZXJpb3JlcyANCmxpYnJhcnkoc3ApDQpsaWJyYXJ5KG1hcHMpDQpsaWJyYXJ5KHJhc3RlcikNCmxpYnJhcnkodG1hcHRvb2xzKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShjYXJ0b2dyYXBoeSkNCmBgYA0KDQojIyAyLiBSZXByZXNlbnRhY2nDs24gZGUgbWFwYXMgY29uIGVsIHBhcXVldGUgKip0bWFwKioNCg0KQXVucXVlIGhheSBtdWNob3MgbGlicmVyw61hcyBwYXJhIGxhIHJlcHJlc2VudGFjacOzbiBkZSBtYXBhcyAoY29tbyBgZ2dtYXBgIHF1ZSB5YSBoZW1vcyB1dGlsaXphZG8pLCBoYXkgbGlicmVyw61hcyBtw6FzIGVzcGVjw61maWNhcyBxdWUgZmFjaWxpdGFyw6FuIG11Y2hhcyB0YXJlYXMuIFVuYSBkZSBlbGxhcywgcXVlIG1hbnRpZW5lIGxhIGZpbG9zb2bDrWEgZGUgZ2dwbG90LCBlcyAqKnRtYXAqKi4gRXN0YSBsaWJyZXLDrWEgYWNlcHRhIG9iamV0b3MgdGlwbyAqKnNwKiosIG11Y2hvIG3DoXMgZWZpZWNpZW50ZXMgcXVlIGxvcyBkYXRhIGZyYW1lcy4NCg0KKipOT1RBOioqIEVzdGEgbGlicmVyw61hIGhhIGNhbWJpYWRvIGEgcHJpbmNpcGlvcyBkZSAyMDE5IHkgbG9zIG9iamV0b3MgYWxtYWNlbmFkb3Mgc29uIGRlIHRpcG8gKipzZioqLCBlc3RvIGFmZWN0YSBhbCBmb3JtYXRvIGRlIGxvcyBkYXRvcy4gU2kgcHJvY2VkZSBzZSBwdWVkZW4gY2FtYmlhciBhIGZvcm1hdG8gKipzcCoqIGNvbiBsYSBmdW5jacOzbiAqKmFzKC4uLiwgJ1NwYXRpYWwnKSoqLg0KDQpbUmVmZXJlbmNpYV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtY2hhbmdlcy12Mi5odG1sKQ0KX3NmIChzaW1wbGUgZmVhdHVyZXMpIG9iamVjdHMgaGF2ZSBhIHNpbXBsZXIgc3RydWN0dXJlIHRoYW4gc3Agb2JqZWN0cy4gQW4gc2Ygb2JqZWN0IGlzIG5vdGhpbmcgbW9yZSB0aGFuIGEgZGF0YS5mcmFtZSB3aXRoIGEgc3BlY2lhbCBnZW9tZXRyeSBjb2x1bW4gdGhhdCBjb250YWlucyB0aGUgZ2VvbWV0cmllcyBmb3IgdGhlIGNvcnJlc3BvbmRpbmcgcm93cy4gU3VjaCBhIGdlb21ldHJ5IGNhbiBiZSBvZiB0eXBlIHNwYXRpYWwgcG9pbnQocyksIGxpbmUocykgb3IgcG9seWdvbihzKSBvciBhbnkgY29tYmluYXRpb24gb2YgdGhlc2UgaW4gYSDigJhnZW9tZXRyeWNvbGxlY3Rpb27igJkgKHNlZSB2aWduZXR0ZSgic2YxIikpLiBUaGUgbGF5ZXJzIGZ1bmN0aW9ucywgc3VjaCBhcyB0bV9wb2x5Z29ucywgd2lsbCBvbmx5IGRyYXcgd2hhdCB0aGV5IGFyZSBzdXBwb3NlZCB0byBkcmF3IChpbiB0aGlzIGNhc2UgcG9seWdvbnMpLiBUaGUgbmV3bHkgYWRkZWQgbGF5ZXIgZnVuY3Rpb24gdG1fc2Ygd2lsbCBkcmF3IGFsbCBnZW9tZXRyaWVzXw0KDQoNCioqUHJpbWVyb3MgcGFzb3MgY29uIHRtYXA6KiouIFtNYXBhcyB1c2FuZG8gdG1hcF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sKQ0KDQoqKkluZm9ybWFjacOzbiBjb21wbGV0YSBlbiBlbCBsaWJybyBncmF0dWl0bzoqKiBbR2VvY29tcHV0YXRpb24gd2l0aCBSXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0LykNCg0KDQpVc2FyZW1vcyB1biBmb3JtYXRvIGRlIHJlcHJlc2VudGFjacOzbiBwb3IgY2FwYXMsIHNpbWlsYXIgYSBnZ21hcCB5IGEgZ2dwbG90Og0KDQorICoqdG1fc2hhcGUoKSA8LS0+IGdncGxvdCgpKiogRXMgbGEgZm9ybWEgbcOhcyBnZW5lcmFsDQorICoqcXRtKCkgPC0tPiBxcGxvdCgpKiogRXMgbGEgZm9ybWEgYWJyZXZpYWRhDQoNCiMjIyBUaXBvcyBkZSBwcmVzZW50YWNpb25lcyBwYXJhIGRvY3VtZW50b3MgaHRtbA0KDQoqKnRtYXBfbW9kZSgicGxvdCIpKio6IHBhcmEgdmlzdWFsYWNpw7NuIGVzdMOhdGljYQ0KKip0bWFwX21vZGUoInZpZXciKSoqOiBwYXJhIHZpc3VhbGl6YWNpw7NuIGludGVyYWN0aXZhLiBQZXJtaXRlIGFjdGl2YXIgYHBvcC11cGANCg0KTGEgb3BjacOzbiBzZWxlY2Npb25hZGEgc2UgbWFudGllbmUgYWN0aXZhIGhhc3RhIHF1ZSBzZSBpbmRpcXVlIGxvIGNvbnRyYXJpby4NCg0KRWplbXBsbyBkZSBtYXBhcyBlbiBtb2RvIHBsb3Q6DQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCJwbG90IikNCmRhdGEoV29ybGQpDQp0bV9zaGFwZShXb3JsZCkgKw0KICB0bV9wb2x5Z29ucygiSFBJIikgIyBIYXBweSBQbGFuZXQgSW5kZXgNCmBgYA0KDQpFamVtcGxvIGRlIG1hcGFzIGVuIG1vZG8gdmlldzoNCg0KYGBge3J9DQp0bWFwX21vZGUoInZpZXciKQ0KZGF0YShXb3JsZCkNCiNXb3JsZDwtYXMoV29ybGQsJ1NwYXRpYWwnKSAjIE5vIG5lY2VzYXJpbywgYWNlcHRhIG9iamV0b3Mgc3AgeSBzZg0KdG1fc2hhcGUoV29ybGQpICsNCiAgdG1fcG9seWdvbnMoIkhQSSIsIGlkID0gImlzb19hMyIsIHBvcHVwLnZhcnMgPSBUUlVFKQ0KDQpgYGANCg0KIyMjIEVqZW1wbG8uIEdyw6FmaWNvIGLDoXNpY28uIFF1aWNrIFRoZW1hdGljIE1hcCAocXRtKQ0KDQpMb3MgZGF0b3MgdXRpbGl6YWRvcyBlc3TDoW4gZGlzcG9uaWJsZXMgZW4gbGEgbGlicmVyw61hICoqdG1hcCoqIHkgdGFtYmnDqW4gZW4gW2h0dHA6Ly93d3cubmF0dXJhbGVhcnRoZGF0YS5jb20vXShodHRwOi8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tLykgDQoNCioqTk9UQSoqOiBFbiBsYSDDumx0aW1hIHZlcnPDs24gZGUgYHRtYXBgbG9zIGZpY2hlcm9zIHZpZW5lbiBlbiBmb3JtYXRvIGBzZmAgZW4gbHVnYXIgZGUgYHNwYC4gRGVzcHXDqXMgZGUgbGEgY2FyZ2EgbG9zIHRyYXNmb3JtYXJlbW9zIGEgY2xhc2UgYHNwYGNvbiBsYSBmdW5jacOzbiBgYXNgIHBvciBlamVtcGxvICoqV29ybGQ8LWFzKFdvcmxkLCAnU3BhdGlhbCcpKiosc2kgYmllbiBsYXMgZnVuY2lvbmVzIGRlIHJlcHJlc2VudGFjacOzbiBkZWwgcGFxdWV0ZSAqdG1hcCogYWNlcHRhIGFtYm9zIGZvcm1hdG9zLg0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHRtYXApDQoNCmRhdGEoV29ybGQsIHJpdmVycywgbWV0cm8pDQoNCnN0cihXb3JsZCkNCnN0cihyaXZlcnMpDQpzdHIobWV0cm8pDQpgYGANCg0KT2JzZXJ2YXIgbGEgZGlmZXJlbmNpYSBlbnRyZSBsYXMgZXN0cnVjdHVyYXMgZGUgKipzZioqIHkgKipzcCoqLg0KDQpgYGB7cn0NCldvcmxkPC1hcyhXb3JsZCwgJ1NwYXRpYWwnKSAgIyBjb252aWVydGUgZGUgc2YgYSBzcA0Kcml2ZXJzPC1hcyhyaXZlcnMsICdTcGF0aWFsJykNCm1ldHJvPC1hcyhtZXRybywgJ1NwYXRpYWwnKQ0KDQoNCnN0cihXb3JsZCxtYXgubGV2ZWwgPSAyKQ0Kc3RyKFdvcmxkQGRhdGEpICMgUGFyYSBmb3JtYXRvIHNwLCBzaSBtYW50ZW5lbW9zIGVsIGZvcm1hdG8gc2YsIHNlIGFjY2VkZSBkaXJlY3RhbWVudGUgYSBsYSB2YXJpYWJsZSBjb21vIGVuIHVuIGRhdGFmcmFtZSBub3JtYWwNCiNnZHBfbWQ6IHByb2R1Y3RvIGludGVyaW9yIGJydXRvDQojZ2RwX2NhcDogcGVyIGNhcGl0YQ0KI0hQSTogSGFwcHkgUGxhbmV0IEluZGV4IChHcmFkbyBkZSBmZWxpY2lkYWQpDQoNCiMgc29sbyBlbCBtYXBhDQpxdG0oc2hwPVdvcmxkKSAjIHNocDogbWFwYS4gQ2FtYmlhciBwb3IgIG1ldHJvLCByaXZlcnMuLi4NCmBgYA0KDQoNCkVqZXJjaWNpbzogRGlidWphIGVsIG1hcGEgZGUgRXNwYcOxYSBjb24gcXRtKCksIGVuIG1vZG8gaW50ZXJhY3Rpdm8uDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCd2aWV3JykNCnF0bShXb3JsZFtXb3JsZCRuYW1lPT0nU3BhaW4nLF0pDQpgYGANCg0KIyMjIENob3JvcGxldGggIE1hcC4NCg0KT2JzZXJ2YSBlbCBjw7NkaWdvIHkgY8OzbW8gbGFzIG9wY2lvbmVzIGluY2x1aWRhcyBtb2RpZmljYW4gZWwgYXNwZWN0byBkZWwgbWFwYS4NCg0KYGBge3J9DQojIGNob3JvcGxldGgNCnF0bShXb3JsZCwgZmlsbCA9ICJlY29ub215IiwgZm9ybWF0PSJXb3JsZCIsIHN0eWxlPSJjb2xfYmxpbmQiKQ0KYGBgDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCd2aWV3JykNCnF0bShXb3JsZCwgZmlsbD0iSFBJIiwgZmlsbC5uPTksIGZpbGwucGFsZXR0ZT0iZGl2IiwgZmlsbC5hdXRvLnBhbGV0dGUubWFwcGluZz1GQUxTRSwgDQoJZmlsbC50aXRsZT0iSGFwcHkgUGxhbmV0IEluZGV4IiwgZmlsbC5pZD0ibmFtZSIsIGZvcm1hdD0iV29ybGQiLCBzdHlsZT0iZ3JheSIpDQoNCnF0bShXb3JsZCwgZmlsbD0iYXJlYSIsIGZpbGwubj05LCBmaWxsLnBhbGV0dGU9ImRpdiIsIGZpbGwuYXV0by5wYWxldHRlLm1hcHBpbmc9VFJVRSwgDQoJZmlsbC50aXRsZT0iQXJlYSIsIGZpbGwuaWQ9ImFyZWEiLCBmb3JtYXQ9IldvcmxkIiwgc3R5bGU9ImdyYXkiKQ0KDQpgYGANCg0KIyMjIEJ1YmJsZSBtYXANClN1cGVycG9uZW1vcyBkb3Mgb2JqZXRvcyBTUCBlbiB1biBtYXBhLiBGaWxvc29mw61hIGFuw6Fsb2dhIGEgYGdncGxvdGAuDQoNCmBgYHtyfQ0KcXRtKFdvcmxkLCBib3JkZXJzID0gTlVMTCkgKyANCnF0bShtZXRybywgc3ltYm9scy5zaXplID0gInBvcDIwMTAiLCANCiAgICBzeW1ib2xzLnRpdGxlLnNpemU9ICJNZXRyb3BvbGl0YW4gQXJlYXMiLCANCiAgICBzeW1ib2xzLmlkPSAibmFtZSIsDQogICAgZm9ybWF0ID0gIldvcmxkIikNCmBgYA0KDQojIyMgRWplbXBsbyBVc2FuZG8gbGEgZm9ybWEgZ2VuZXJhbCAqKnRtX1hYWCoqLg0KDQpVc2Ftb3MgZWwgb2JqZXRvICoqY291bnRyaWVzX3NwZGYqKi4gRW4gcHJpbWVyIGx1Z2FyIGF2ZXJpZ3VhbW9zIGxhcyB2YXJpYWJsZXMgYWxtYWNlbmFkYXMgZW4gZWwgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQpsaWJyYXJ5KHNwKQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShnZW9zcGF0aWFsKQ0KDQpzdHIoY291bnRyaWVzX3NwZGYsbWF4LmxldmVsID0gMikNCnN0cihjb3VudHJpZXNfc3BkZkBkYXRhKQ0KDQpgYGANCg0KTGEgY2FwYSAqKnRtX3NoYXBlKCkqKiBlcyBjb21vIGxhIGZ1bmNpw7NuIGdncGxvdCgpLiBDb250aWVuZSBlbCBvYmpldG8gKipzcCoqLyoqc2YqKiBjb24gbG9zIGRhdG9zIHkgZXMgbGEgYmFzZSBzb2JyZSBsYSBxdWUgc2Ugc3VwZXJwb25lbiBsYXMgZGVtw6FzIGNhcGFzLiBQb3N0ZXJpb3JtZW50ZSB2ZXJlbW9zIHF1ZSBwb2RlbW9zIHN1cGVycG9uZXIgdmFyaWFzIGNhcGFzYSAqKnRtX3NoYXBlKCkqKiBkZSBkaWZlcmVudGVzIG9iamV0b3MgKipzcCoqIGVuIHVuIG1pc21vIG1hcGEuDQoNCkxhIGNhcGEgKip0bV9maWxsKCkqKiBoYWNlIGVsIHJlbGxlbm8gZGUgbG9zIHBvbMOtZ29ub3MgZGUgbWFuZXJhIHNpbWlsYXIgYSAqKnRtX3BvbHlnb24oY29sID0gIi4uLiIpKiouIFZlYW1vcyB1biBlamVtcGxvLg0KDQpgYGB7cn0NCiAgcDwtdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpIA0KICBwPC1wK3RtX2ZpbGwoY29sID0gInBvcHVsYXRpb24iLCBzdHlsZT0icXVhbnRpbGUiKSAjIGNvbnN1bHRhIGVuIHF1w6kgY29uc2lzdGUgZWwgZXN0aWxvICJxdWFudGlsZSINCiAgc2hvdyhwKQ0KYGBgDQpTaW50YXhpcyBhbHRlcm5hdGl2YQ0KDQpgYGB7cn0NCiAgdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsc3R5bGU9InF1YW50aWxlIikgDQpgYGANCg0KQcOxYWRlIHVuYSBjYXBhICoqdG1fYm9yZGVycygpKiogY29uIGVsIGF0cmlidXRvIGNvbCA9ICJidXJseXdvb2QiLiBPYnNlcnZhIGVsIGVmZWN0by4NCg0KYGBge3J9DQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3BvcHVsYXRpb24nKQ0KDQpgYGANCg0KQcOxYWRlIHVuYSBjYXBhICoqdG1fYnViYmxlcygpKiogY29uIHNpemUgPSAicG9wdWxhdGlvbiIgeSBjb2w9ImdyZWVuIi4gQ2FtYmlhIGVsIGNvbG9yIGRlIGxhcyBmcm9udGVyYXMgYSAiYmxhY2siDQoNCg0KYGBge3J9DQoNCg0KYGBgDQoNCg0KIyMjIE90cm8gZWplbXBsbyBkZSBDaG9yb3BsZXRoDQoNCkFxdcOtIHJlcHJlc2VudGFtb3MgZG9zIG1hZ25pdHVkZXM6ICJ3ZWxsIGJlaW5nIiBlbiBlbCBjb2xvciBkZSBsb3MgcG9sw61nb25vcywgeSAiYXJlYSIsIHByb3BvcmNpb25hbCBhbCB0YW1hw7FvIGRlbCB0ZXh0by4NCg0KYGBge3J9DQpkYXRhKFdvcmxkKQ0KRXVyb3BlPC1Xb3JsZFtXb3JsZCRjb250aW5lbnQ9PSdFdXJvcGUnLF0NCg0KdG1hcF9tb2RlKCdwbG90JykgIyBlbiBtb2RvIHZpZXcgbm8gZnVuY2lvbmEgZWwgZXNjYWxhZG8gZGUgbGFzIGV0aXF1ZXRhcw0KdG1fc2hhcGUoRXVyb3BlKSArDQogICAgdG1fcG9seWdvbnMoIndlbGxfYmVpbmciLCB0ZXh0TkE9Ik5vbi1FdXJvcGVhbiBjb3VudHJpZXMiLCB0aXRsZT0iV2VsbC1CZWluZyBJbmRleCIpICsNCiAgICB0bV90ZXh0KCJpc29fYTMiLCBzaXplPSJBUkVBIiwgcm9vdD01KSArIA0KdG1fZm9ybWF0KCJXb3JsZCIpICsNCnRtX3N0eWxlKCJncmV5IikNCmBgYA0KDQoqKk5PVEE6KiogTm8gZXN0w6EgZGlzcG9uaWJsZSBlbCBtYXBhICdFdXJvcGUnIGNvbW8gdGFsIGVuIGxhIMO6bHRpbWEgdmVyc2nDs24gZGUgdG1hcCAobm8gcG9kZW1vcyBoYWNlciBkYXRhKEV1cm9wZSksIHNpbm8gcXVlIGRlYmVtb3MgcmVzdHJpbmdpciAnV29ybGQnIHBvciBjb250aW5lbnRlLg0KDQoNCiMjIyBFamVtcGxvIGRlIFN1cGVycG9zaWNpw7NuIGRlIG3Dumx0aXBsZXMgb2JqZXRvcyAqKnNwKiogZW4gdW4gbWlzbW8gbWFwYS4NCg0KQ29tYW5kb3MgcGFyYSBsYSByZXByZXNlbnRhY2nDs24gZGUgbWFwYXMgY29uIFt0bWFwXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdG1hcC92aWduZXR0ZXMvdG1hcC1nZXRzdGFydGVkLmh0bWwpOiANCg0KT2JzZXJ2YSBlbCBwcm9jZWRpbWllbnRvIHBhcmEgc3VwZXJwb25lciBtw7psdGlwbGVzIG9iamV0b3MgKCoqU3BhdGlhbFBvaW50cyoqLCAqKlNwYXRpYWxMaW5lcyoqIHkgKipTcGF0aWFsUG9seWdvbnMqKikgZW4gdW4gbWlzbW8gbWFwYS4NCg0KRW4gcHJpbWVyIGx1Z2FyIG9ic2VydmFtb3MgbGEgZXN0cnVjdHVyYSBkZSBjYWRhIHVubyBkZSBsb3Mgb2JqZXRvcw0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgbG9zIGRhdG9zIChjb24gY29uanVudG9zIGluY2x1aWRvcyBlbiBsYXMgbGlicmVyw61hcykNCiNkYXRhKHBhY2thZ2U9J3RtYXAnKQ0KZGF0YShsYW5kLCByaXZlcnMsIG1ldHJvKSAgIyBsYW5kIGNvdmVyLCByaXZlcnMsIG1ldHJvcG9saXRhbiBhcmVhcw0KIyBsYW5kIGVzIGRlIHRpcG8gcmFzdGVyIChzdGFycy1yYXN0ZXIpLCBsYXMgb3RyYXMgZG9zIHNvbiBkZSB0aXBvIHNmDQoNCiMgVHJhbnNmb3JtYW1vcyBTRiBlbiBTUA0Kcml2ZXJzPC1hcyhyaXZlcnMsJ1NwYXRpYWwnKQ0KbWV0cm88LWFzKG1ldHJvLCAnU3BhdGlhbCcpDQoNCiMgU2Fsdm8gcGFyYSB1c2FyIGVzdG9zIGPDs2RpZ29zIG5vIGVzIG5lY2VzYXJpbyBwYXNhciBhIHRpcG8gc3ANCnN0cihsYW5kLG1heC5sZXZlbCA9IDIpDQojI3N0cihsYW5kQGRhdGEpDQpzdHIocml2ZXJzLG1heC5sZXZlbCA9IDIpDQpzdHIocml2ZXJzQGRhdGEpDQpzdHIobWV0cm8sbWF4LmxldmVsID0gMikNCnN0cihtZXRyb0BkYXRhKQ0KYGBgDQoNCg0KUmVwcmVzZW50YW1vcyB0b2RvcyBzaW11bHTDoW5lYW1lbnRlIChsdWVnbyBleHBsaWNhcmVtb3MgcGFzbyBhIHBhc28pDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCnRtX3NoYXBlKGxhbmQpICsgDQogICAgdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpICsgDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCiAgICB0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSArDQogICAgdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUobWV0cm8pICsNCiAgICB0bV9idWJibGVzKCJwb3AyMDEwIiwgInJlZCIsIGJvcmRlci5jb2wgPSAiYmxhY2siLCBib3JkZXIubHdkPTEsIA0KICAgICAgICBzaXplLmxpbSA9IGMoMCwgMTFlNiksIHNpemVzLmxlZ2VuZCA9IGMoMWU2LCAyZTYsIDRlNiwgNmU2LCAxMGU2KSwgDQogICAgICAgIHRpdGxlLnNpemU9Ik1ldHJvcG9saXRhbiBQb3B1bGF0aW9uIikgKw0KICAgIHRtX3RleHQoIm5hbWUiLCBzaXplPSJwb3AyMDEwIiwgc2NhbGU9MSwgcm9vdD00LCBzaXplLmxvd2VyYm91bmQgPSAuNiwgDQogICAgICAgIGJnLmNvbG9yPSJ3aGl0ZSIsIGJnLmFscGhhID0gLjc1LCANCiAgICAgICAgYXV0by5wbGFjZW1lbnQgPSAxLCBsZWdlbmQuc2l6ZS5zaG93ID0gRkFMU0UpICsgDQp0bV9mb3JtYXQoIldvcmxkIikgKw0KdG1fc3R5bGUoIm5hdHVyYWwiKQ0KDQpgYGANClBhc28gYSBwYXNvDQoNCmBgYHtyfQ0KIyBjYXBhIGRlIGxhbmQgY292ZXIgY29uIHVuYSBjYXBhIHJhc3RlciBkZSBwb3JjZW50YWplIGRlIHN1cGVyZmljaWUgY3ViaWVydGEgcG9yIMOhcmJvbGVzIChncmFkaWVudGUgZGUgY29sb3IgZGFkbyBwb3IgYnJlYWtzKQ0KIHA8LXRtX3NoYXBlKGxhbmQpICsgDQogICAgdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpDQogIHNob3cocCkNCmBgYA0KDQpQYXNvIDENCmBgYHtyfQ0KIyBBw7FhZGlyIGNhcGEgZGUgZGF0b3MgZGUgRXVyb3BhIHkgZGVmaW5pcmxvIGNvbW8gY2FwYSBtYWVzdHJhLCBtb3N0cmFyIHN1cyBmcm9udGVyYXMNCg0KcDwtdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSAgIA0Kc2hvdyhwKQ0KYGBgDQoNClBhc28gMg0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIHLDrW9zLCBlc2NhbGFkb3MgcG9yIHN1IGNhdWRhbA0KdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSArDQp0bV9zaGFwZShyaXZlcnMpKw0KdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkNCg0KYGBgDQoNClBhc28gMw0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIGJ1cmJ1amFzLCBkZSB0YW1hw7FvIHByb3BvcmNpb25hbCBhIGxhIHBvYmxhY2nDs24gbWV0cm9wb2xpdGFuYQ0KdG1fc2hhcGUobGFuZCkgKyANCnRtX3Jhc3RlcigidHJlZXMiLCBicmVha3M9c2VxKDAsIDEwMCwgYnk9MjApLCBsZWdlbmQuc2hvdyA9IEZBTFNFKSArDQp0bV9zaGFwZShFdXJvcGUsIGlzLm1hc3RlciA9IFRSVUUpICsNCnRtX2JvcmRlcnMoKSArDQp0bV9zaGFwZShyaXZlcnMpKw0KdG1fbGluZXMobHdkPSJzdHJva2Vsd2QiLCBzY2FsZT01LCBsZWdlbmQubHdkLnNob3cgPSBGQUxTRSkrDQp0bV9zaGFwZShtZXRybykrDQp0bV9idWJibGVzKCJwb3AyMDEwIiwgInJlZCIsIGJvcmRlci5jb2wgPSAiYmxhY2siLCBib3JkZXIubHdkPTEsIA0KICAgICAgICBzaXplLmxpbSA9IGMoMCwgMTFlNiksIHNpemVzLmxlZ2VuZCA9IGMoMWU2LCAyZTYsIDRlNiwgNmU2LCAxMGU2KSwgDQogICAgICAgIHRpdGxlLnNpemU9Ik1ldHJvcG9saXRhbiBQb3B1bGF0aW9uIikgDQogICAgICAgIA0KYGBgDQoNClBhc28gNA0KDQpgYGB7cn0NCiMgQcOxYWRpciBjYXBhIGRlIHRleHRvIGRlIG5vbWJyZXMgZGUgbWV0csOzcG9saXMNCnRtX3NoYXBlKGxhbmQpICsgDQp0bV9yYXN0ZXIoInRyZWVzIiwgYnJlYWtzPXNlcSgwLCAxMDAsIGJ5PTIwKSwgbGVnZW5kLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUoRXVyb3BlLCBpcy5tYXN0ZXIgPSBUUlVFKSArDQp0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSsNCnRtX2xpbmVzKGx3ZD0ic3Ryb2tlbHdkIiwgc2NhbGU9NSwgbGVnZW5kLmx3ZC5zaG93ID0gRkFMU0UpKw0KdG1fc2hhcGUobWV0cm8pKw0KdG1fYnViYmxlcygicG9wMjAxMCIsICJyZWQiLCBib3JkZXIuY29sID0gImJsYWNrIiwgYm9yZGVyLmx3ZD0xLCANCiAgICAgICAgc2l6ZS5saW0gPSBjKDAsIDExZTYpLCBzaXplcy5sZWdlbmQgPSBjKDFlNiwgMmU2LCA0ZTYsIDZlNiwgMTBlNiksIA0KICAgICAgICB0aXRsZS5zaXplPSJNZXRyb3BvbGl0YW4gUG9wdWxhdGlvbiIpKyANCiB0bV90ZXh0KCJuYW1lIiwgc2l6ZT0icG9wMjAxMCIsIHNjYWxlPTEsIHJvb3Q9NCwgc2l6ZS5sb3dlcmJvdW5kID0gLjYsIA0KICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5hbHBoYSA9IC43NSwgDQogICAgICAgIGF1dG8ucGxhY2VtZW50ID0gMSwgbGVnZW5kLnNpemUuc2hvdyA9IEZBTFNFKQ0KICAgICAgICAgDQogDQpgYGANCg0KUGFzbyA1DQoNCmBgYHtyfQ0KIyBBw7FhZGlyIGZvcm1hdG86IGF2YWlsYWJsZSBmb3JtYXRzIGFyZTogIldvcmxkIiwgIldvcmxkX3dpZGUiLCAiTkxEIiwgIk5MRF93aWRlIiANCnRtX3NoYXBlKGxhbmQpICsgDQp0bV9yYXN0ZXIoInRyZWVzIiwgYnJlYWtzPXNlcSgwLCAxMDAsIGJ5PTIwKSwgbGVnZW5kLnNob3cgPSBGQUxTRSkgKw0KdG1fc2hhcGUoRXVyb3BlLCBpcy5tYXN0ZXIgPSBUUlVFKSArDQp0bV9ib3JkZXJzKCkgKw0KdG1fc2hhcGUocml2ZXJzKSsNCnRtX2xpbmVzKGx3ZD0ic3Ryb2tlbHdkIiwgc2NhbGU9NSwgbGVnZW5kLmx3ZC5zaG93ID0gRkFMU0UpKw0KdG1fc2hhcGUobWV0cm8pKw0KdG1fYnViYmxlcygicG9wMjAxMCIsICJyZWQiLCBib3JkZXIuY29sID0gImJsYWNrIiwgYm9yZGVyLmx3ZD0xLCANCiAgICAgICAgc2l6ZS5saW0gPSBjKDAsIDExZTYpLCBzaXplcy5sZWdlbmQgPSBjKDFlNiwgMmU2LCA0ZTYsIDZlNiwgMTBlNiksIA0KICAgICAgICB0aXRsZS5zaXplPSJNZXRyb3BvbGl0YW4gUG9wdWxhdGlvbiIpKyANCiB0bV90ZXh0KCJuYW1lIiwgc2l6ZT0icG9wMjAxMCIsIHNjYWxlPTEsIHJvb3Q9NCwgc2l6ZS5sb3dlcmJvdW5kID0gLjYsIA0KICAgICAgICBiZy5jb2xvcj0id2hpdGUiLCBiZy5hbHBoYSA9IC43NSwgDQogICAgICAgIGF1dG8ucGxhY2VtZW50ID0gMSwgbGVnZW5kLnNpemUuc2hvdyA9IEZBTFNFKSsgDQp0bV9mb3JtYXQoIldvcmxkIikgDQpgYGANCg0KUGFzbyA2DQoNCmBgYHtyfQ0KIyBlc3RpbG8gZGUgcmVwcmVzZW50YWNpw7NuIChnYW1hIGRlIGNvbG9yZXMpDQp0bV9zaGFwZShsYW5kKSArIA0KdG1fcmFzdGVyKCJ0cmVlcyIsIGJyZWFrcz1zZXEoMCwgMTAwLCBieT0yMCksIGxlZ2VuZC5zaG93ID0gRkFMU0UpICsNCnRtX3NoYXBlKEV1cm9wZSwgaXMubWFzdGVyID0gVFJVRSkgKw0KdG1fYm9yZGVycygpICsNCnRtX3NoYXBlKHJpdmVycykrDQp0bV9saW5lcyhsd2Q9InN0cm9rZWx3ZCIsIHNjYWxlPTUsIGxlZ2VuZC5sd2Quc2hvdyA9IEZBTFNFKSsNCnRtX3NoYXBlKG1ldHJvKSsNCnRtX2J1YmJsZXMoInBvcDIwMTAiLCAicmVkIiwgYm9yZGVyLmNvbCA9ICJibGFjayIsIGJvcmRlci5sd2Q9MSwgDQogICAgICAgIHNpemUubGltID0gYygwLCAxMWU2KSwgc2l6ZXMubGVnZW5kID0gYygxZTYsIDJlNiwgNGU2LCA2ZTYsIDEwZTYpLCANCiAgICAgICAgdGl0bGUuc2l6ZT0iTWV0cm9wb2xpdGFuIFBvcHVsYXRpb24iKSsgDQogdG1fdGV4dCgibmFtZSIsIHNpemU9InBvcDIwMTAiLCBzY2FsZT0xLCByb290PTQsIHNpemUubG93ZXJib3VuZCA9IC42LCANCiAgICAgICAgYmcuY29sb3I9IndoaXRlIiwgYmcuYWxwaGEgPSAuNzUsIA0KICAgICAgICBhdXRvLnBsYWNlbWVudCA9IDEsIGxlZ2VuZC5zaXplLnNob3cgPSBGQUxTRSkrIA0KdG1fZm9ybWF0KCJXb3JsZCIpICsNCnRtX3N0eWxlKCJuYXR1cmFsIikNCiANCmBgYA0KIyMjIyBDb25zaWRlcmFjaW9uZXMgc29icmUgZWwgbWFwYSBhbnRlcmlvcg0KDQorIEVzdGUgbWFwYSB0aWVuZSA0IGdydXBvcyBkZSBjYXBhcywgcmVzcGVjdGl2YW1lbnRlIGxvcyBvYmpldG9zICoqbGFuZCwgRXVyb3BlLCByaXZlcnMsIHkgbWV0cm8qKi4gRWwgb3JkZW4gZGUgZ3J1cG8gKGNhcGEpIGNvcnJlc3BvbmRlIGFsIG9yZGVuIGVuIHF1ZSBzZSBkaWJ1amEuDQorIExvcyBvYmpldG9zIHB1ZWRlbiB0ZW5lciAqKmRpZmVyZW50ZXMgcHJveWVjY2lvbmVzKiogeSB0YW1iacOpbiBwdWVkZW4gY3VicmlyIGRpZmVyZW50ZXMgw6FyZWFzICgqKmJvdW5kaW5nIGJveGVzKiopLiBUYW50byBsYSBgcHJveWVjY2nDs25gIGNvbW8gYGVsIMOhcmVhIGN1YmllcnRhYCBzZSB0b21hbiBwb3IgZGVmZWN0byBkZWwgb2JqZXRvIGRlZmluaWRvIGVuIGxhICoqcHJpbWVyYSB0bV9zaGFwZSoqLCBwZXJvIGVuIGVzdGUgY2FzbyBlbiBsYSBzZWd1bmRhICoqdG1fc2hhcGUqKiB5YSBxdWUgZXMgZW4gbGEgcXVlIHNlIGhhIHVzYWRvICoqaXMubWFzdGVyPVRSVUUqKi4gVGVuIGVuIGN1ZW50YSBxdWUgdG9kb3MgbG9zIG9iamV0b3MgdGllbmVuIGVsZW1lbnRvcyBmdWVyYSBkZSBFdXJvcGEgKHZlciBwb3IgZWplbXBsbyAqKnF0bShyaXZlcnMpKiopLiANCg0KU2UgcHVlZGUgYWRlbcOhcyBhw7FhZGlyIHVuYSBjYXBhICoqdG1fbGF5b3V0KCkqKiBxdWUgY29udHJvbGEgYXNwZWN0b3MgY29tbyB0w610dWxvLCBtw6FyZ2VuZXMsIHJlbGFjacOzbiBkZSBhc3BlY3RvLCBldGMuDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCnRtX3NoYXBlKHJpdmVycykrdG1fbGluZXMoKSt0bV9sYXlvdXQobWFpbi50aXRsZSA9IlLDrW9zIixtYWluLnRpdGxlLnBvc2l0aW9uID0gImNlbnRlciIpDQojIEdyb3NvciBkZSBsYSBsw61uZWEgZGVwZW5kaWVudGUgZGUgdW5hIHZhcmlhYmxlLg0KdG1fc2hhcGUocml2ZXJzKSt0bV9saW5lcyhsd2Q9InN0cm9rZWx3ZCIsIHNjYWxlPTUsIGxlZ2VuZC5sd2Quc2hvdyA9IEZBTFNFKSt0bV9sYXlvdXQobWFpbi50aXRsZSA9IlLDrW9zIixtYWluLnRpdGxlLnBvc2l0aW9uID0gImNlbnRlciIpDQp0bV9zaGFwZShtZXRybykrdG1fZG90cygpK3RtX2xheW91dChtYWluLnRpdGxlID0iTWV0cm8iLG1haW4udGl0bGUucG9zaXRpb24gPSAiY2VudGVyIikNCmBgYA0KDQojIyMgRWplbXBsb3M6DQoNClJlcHJlc2VudGFjacOzbiBkZSBkb3MgbWFwYXMgc2ltdWx0w6FuZW1lbnRlIGNvbiBhc2lnbmFjacOzbiBkZSBDUlMNCg0KYGBge3J9DQojIFByb3llY2Npw7NuIFJvYmluc29uDQp0bWFwX21vZGUoJ3Bsb3QnKQ0Kcm9iaW4gPC0gIitwcm9qPXJvYmluICtsb25fMD0wICt4XzA9MCAreV8wPTAgK2VsbHBzPVdHUzg0ICtkYXR1bT1XR1M4NCArdW5pdHM9bSArbm9fZGVmcyINCg0KbTEgPC0gdG1fc2hhcGUoV29ybGQsIHByb2plY3Rpb24gPSByb2JpbikgKw0KICB0bV9wb2x5Z29ucyhjKCJIUEkiLCAiZ2RwX2NhcF9lc3QiKSwNCiAgICAgICAgICAgICAgcGFsZXR0ZSA9IGxpc3QoIlJkWWxHbiIsICJQdXJwbGVzIiksDQogICAgICAgICAgICAgIHN0eWxlID0gYygicHJldHR5IiwgImZpeGVkIiksIG4gPSA3LCANCiAgICAgICAgICAgICAgYnJlYWtzID0gbGlzdChOVUxMLCBjKDAsIDUwMCwgMjAwMCwgNTAwMCwgMTAwMDAsIDI1MDAwLCA1MDAwMCwgSW5mKSksDQogICAgICAgICAgICAgIHRpdGxlID0gYygiSGFwcHkgUGxhbmV0IEluZGV4IiwgIkdEUCBwZXIgY2FwaXRhIikpICsNCiAgdG1fc3R5bGUoIm5hdHVyYWwiLCBlYXJ0aC5ib3VuZGFyeSA9IGMoLTE4MCwgLTg3LCAxODAsIDg3KSkgICsNCiAgdG1fZm9ybWF0KCJXb3JsZCIsIGlubmVyLm1hcmdpbnMgPSAwLjAyLCBmcmFtZSA9IEZBTFNFKSArDQogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIiksIGJnLmNvbG9yID0gImdyYXk5NSIsIGZyYW1lID0gVFJVRSkgKw0KICB0bV9jcmVkaXRzKGMoIiIsICJSb2JpbnNvbiBwcm9qZWN0aW9uIiksIHBvc2l0aW9uID0gYygiUklHSFQiLCAiQk9UVE9NIikpDQptMQ0KYGBgDQoNClZpc3VhbGl6YWNpw7NuIGRlIHRyZXMgbWFnbml0dWRlcyBlbiBlbCBtaXNtbyBtYXBhDQoNCmBgYHtyfQ0KdG1hcF9tb2RlKCdwbG90JykNCm1ldHJvJGdyb3d0aCA8LSAobWV0cm8kcG9wMjAyMCAtIG1ldHJvJHBvcDIwMTApIC8gKG1ldHJvJHBvcDIwMTAgKiAxMCkgKiAxMDANCg0KbTIgPC0gdG1fc2hhcGUoV29ybGQpICsNCiAgICB0bV9wb2x5Z29ucygiaW5jb21lX2dycCIsIHBhbGV0dGUgPSAiLUJsdWVzIiwgDQogICAgICB0aXRsZSA9ICJJbmNvbWUgY2xhc3MiLCBjb250cmFzdCA9IDAuNywgYm9yZGVyLmNvbCA9ICJncmV5MzAiLCBpZCA9ICJuYW1lIikgKw0KICAgIHRtX3RleHQoImlzb19hMyIsIHNpemUgPSAiQVJFQSIsIGNvbCA9ICJncmV5MzAiLCByb290ID0gMykgKw0KICB0bV9zaGFwZShtZXRybykgKw0KICAgIHRtX2J1YmJsZXMoInBvcDIwMTAiLCBjb2wgPSAiZ3Jvd3RoIiwgYm9yZGVyLmNvbCA9ICJibGFjayIsDQogICAgICBib3JkZXIuYWxwaGEgPSAwLjUsDQogICAgICBicmVha3MgPSBjKC1JbmYsIDAsIDIsIDQsIDYsIEluZikgLA0KICAgICAgcGFsZXR0ZSA9ICItUmRZbEduIiwNCiAgICAgIHRpdGxlLnNpemUgPSAiTWV0cm8gcG9wdWxhdGlvbiAoMjAxMCkiLCANCiAgICAgIHRpdGxlLmNvbCA9ICJBbm51YWwgZ3Jvd3RoIHJhdGUgKCUpIiwNCiAgICAgIGlkID0gIm5hbWUiLA0KICAgICAgcG9wdXAudmFycyA9IGMoInBvcDIwMTAiLCAicG9wMjAyMCIsICJncm93dGgiKSkgKyANCiAgdG1fc3R5bGUoImdyYXkiKSArDQogIHRtX2Zvcm1hdCgiV29ybGQiLCBmcmFtZS5sd2QgPSAyKQ0KbTINCmBgYA0KDQpGaWphciB1biAibWFya2VyIiBtZWRpYW50ZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMgYSBwYXJ0aXIgZGUgZGlyZWNjacOzbi4NCg0KYGBge3J9DQojIG9idGFpbiBnZW9jb2RlIGFkZHJlc3MgaW5mb3JtYXRpb24NCmV0c2UgPC0gZ2VvY29kZV9PU00oJ0VUU0UsIEJ1cmphc3NvdCwgU3BhaW4nLCAgYXMuc2YgPSBUUlVFKQ0KDQojIGNoYW5nZSB0byBpbnRlcmFjdGl2ZSBtb2RlDQp0bWFwX21vZGUoInZpZXciKQ0KICB0bV9zaGFwZShldHNlKSArDQoJdG1fbWFya2Vycyh0ZXh0PSJxdWVyeSIpDQpgYGANCg0KIyMjIEd1YXJkYXIgdW4gbWFwYTogKip0bWFwX3NhdmUoKSoqDQoNClBlcm1pdGUgZ3JhYmFyIG1hcGFzIGVzdMOhdGljb3MgeSB0YW1iaWVuIElOVEVSQUNUSVZPUy4NCg0KYGBge3J9DQpsaWJyYXJ5KHNwKQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShnZW9zcGF0aWFsKQ0KDQp0bWFwX21vZGUoJ3Bsb3QnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsNCiAgdG1fZ3JpZChuLnggPSAxMSwgbi55ID0gMTEsIHByb2plY3Rpb24gPSAiK3Byb2o9bG9uZ2xhdCIpICsNCiAgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsIHN0eWxlID0gInF1YW50aWxlIixhbHBoYSA9IDAuMikgICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYnVybHl3b29kNCIpDQoNCiMgR3VhcmRhciB1biBtYXBhIEVTVMOBVElDTw0KdG1hcF9zYXZlKGZpbGVuYW1lPSJwb3B1bGF0aW9uLnBuZyIpDQoNCiMgU2F2ZSB1biBtYXBhIElOVEVSQUNUSVZPDQp0bWFwX21vZGUoJ3ZpZXcnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpICsNCiAgdG1fZ3JpZChuLnggPSAxMSwgbi55ID0gMTEsIHByb2plY3Rpb24gPSAiK3Byb2o9bG9uZ2xhdCIpICsNCiAgdG1fZmlsbChjb2wgPSAicG9wdWxhdGlvbiIsIHN0eWxlID0gInF1YW50aWxlIixhbHBoYSA9IDAuMikgICsNCiAgdG1fYm9yZGVycyhjb2wgPSAiYnVybHl3b29kNCIpDQp0bWFwX3NhdmUoZmlsZW5hbWU9InBvcHVsYXRpb24uaHRtbCIpDQoNCiMgTGEgb3BjacOzbiBwb3IgZGVmZWN0bywgY3VhbmRvIHNlIHV0aWxpemEgdG1hcCBlcyBxdWUgZWwgbWFwYSBzZWEgaW50ZXJhY3Rpdm8uDQogIA0KYGBgDQoNCiMjIyBJbnRlZ3JhY2nDs24gZW4gc2hpbnkNCg0KUG9kZW1vcyBpbmNydXN0YXIgbWFwYXMgZGUgdG1hcCBlbiBzaGlueSBjb24gbGEgZnVuY2nDs24gKip0bWFwT3V0cHV0KCkqKiBlbiBsYSBwYXJ0ZSBkZSBVSSB5ICoqcmVuZGVyVG1hcCgpKiogZW4gZWwgc2VydmVyOg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnVpIDwtIGZsdWlkUGFnZSgNCiAgdG1hcE91dHB1dCgibXlfdG1hcCIpDQopDQoNCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCxvdXRwdXQpIHsNCiAgb3V0cHV0JG15X3RtYXAgPSByZW5kZXJUbWFwKHsNCiAgICB0bV9zaGFwZShXb3JsZCwgcHJvamVjdGlvbj0iK3Byb2o9cm9iaW4iKSArIHRtX3BvbHlnb25zKCJIUEkiLCBsZWdlbmQudGl0bGUgPSAiSGFwcHkgUGxhbmV0IEluZGV4IikgKyB0bV9zdHlsZSgnY29iYWx0JykNCiAgfSkNCn0NCg0Kc2hpbnlBcHAodWksIHNlcnZlcikNCg0KYGBgDQoNCg0KDQojIyBFamVyY2ljaW8NCg0KDQoxLiBEaWJ1amEsIGVuIG1vZG8gcGxvdCwgZWwgbWFwYSBkZWwgbXVuZG8gKGNvdW50cmllc19zcGRmKSwgZGUgbGEgbGlicmVyw61hICoqZ2Vvc3BhdGlhbCoqICh1c2EgdG1fWFhYKS4NCg0KYGBge3J9DQp0bWFwX21vZGUoJ3Bsb3QnKQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpK3RtX3BvbHlnb25zKCkNCmBgYA0KDQoyLiBEaWJ1amEgZWwgbWFwYSBkZWwgbXVuZG8gKGNvdW50cmllc19zcGRmKSB5IGNvbG9yZWEgbG9zIHBhaXNlcyBzZWfDum4gZWwgY29udGluZW50ZS4NCg0KYGBge3J9DQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3JlZ2lvbicpDQpgYGANCg0KMy4gQcOxYWRlIGxhcyBpbmljaWFsZXMgZGUgbG9zIHBhaXNlcyAoKippc29fYTMqKikgY29uIHRtX3RleHQgKHVzYSBzaXplPTEpLg0KDQpgYGB7cn0NCnRtX3NoYXBlKGNvdW50cmllc19zcGRmKSt0bV9maWxsKGNvbD0ncmVnaW9uJykrdG1fdGV4dCgnaXNvX2EzJyxzaXplPTEpDQpgYGANCg0KNC4gQcOxYWRlIGxhcyBpbmljaWFsZXMgZGUgbG9zIHBhaXNlcyAoKippc29fYTMqKikgY29uIHVuIHRhbWHDsW8gcXVlIHNlYSBwcm9wb3JjaW9uYWwgYWwgw6FyZWEgZGVsIHBhw61zIChtaXJhIGF5dWRhIGRlICoqdG1fdGV4dCoqKS4gUmVwaXRlIHBlcm8gY29uIGVsIHRleHRvIHByb3BvcmNpb25hbCBhIGxhIHBvYmxhY2nDs24uDQoNCmBgYHtyfQ0KdG1fc2hhcGUoY291bnRyaWVzX3NwZGYpK3RtX2ZpbGwoY29sPSdyZWdpb24nKSt0bV90ZXh0KCdpc29fYTMnLHNpemU9J0FSRUEnKQ0KDQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikrdG1fZmlsbChjb2w9J3JlZ2lvbicpK3RtX3RleHQoJ2lzb19hMycsc2l6ZT0ncG9wdWxhdGlvbicpDQoNCmBgYA0KDQo1LiBEaWJ1amEgbG9zIHBhaXNlcyB5IGNvbG9yw6lhbG9zIHNlZ8O6biBsYSBERU5TSURBRCBkZSBQT0JMQUNJw5NOIChoYWJpdGFudGVzL2ttMikuIExhIGZ1bmNpw7NuICphcmVhKCkqIGRlbCBwYXF1ZXRlICoqcmFzdGVyKiogcGVybWl0ZSBjYWxjdWxhciBlbCDDoXJlYSBkZSBjYWRhIHBvbMOtZ29ubyBkZSB1biBvYmpldG8gU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lLCBlbiBtZXRyb3MgY3VhZHJhZG9zLiBQcnVlYmEgZWwgZWZlY3RvIGRlIGxhIG9wY2nDs24gKipzdHlsZT0ncXVhbnRpbGUnKiogZW4gKip0bV9maWxsKCkqKiwgc29icmUgbGEgZGVuc2lkYWQuDQoNCmBgYHtyfQ0KbGlicmFyeSh0bWFwKQ0KbGlicmFyeShyYXN0ZXIpDQoNCiMgQ2FsY3VsYXIgZWwgw6FyZWEgZGUgY2FkYSBwYcOtcyBlbiBrbV4yDQpjb3VudHJpZXNfc3BkZiRhcmVhX2ttMiA8LSBhcmVhKGNvdW50cmllc19zcGRmKS8xMF42DQoNCiMgQ2FsY3VsYXIgbGEgZGVuc2lkYWQgZGUgcG9ibGFjacOzbg0KY291bnRyaWVzX3NwZGYkZGVuc2l0eSA8LSBjb3VudHJpZXNfc3BkZiRwb3B1bGF0aW9uL2NvdW50cmllc19zcGRmJGFyZWFfa20yDQoNCiMgQ3JlYXIgdW4gbWFwYSBjb24gbGEgZGVuc2lkYWQgZGUgcG9ibGFjacOzbiBjb21vIHZhcmlhYmxlIGRlIGNvbG9yDQp0bV9zaGFwZShjb3VudHJpZXNfc3BkZikgKw0KICB0bV9maWxsKGNvbCA9ICJkZW5zaXR5Iiwgc3R5bGUgPSAicXVhbnRpbGUiKSArDQogIHRtX3RleHQoImlzb19hMyIsIHNpemUgPSAiYXJlYV9rbTIiKSArDQogIHRtX2xheW91dChmcmFtZSA9IEZBTFNFKQ0KYGBgDQoNCjYuIFJlcGl0ZSBlbCBtYXBhIGFudGVyaW9yIHBlcm8gZGlidWphIHNvbG8gRXVyb3BhLCBzaW4gIlJ1c3NpYSIuDQoNCmBgYHtyfQ0KDQpFdXJvcGVfd29fcnVzc2lhIDwtIGNvdW50cmllc19zcGRmW2NvdW50cmllc19zcGRmJG5hbWUhPSdSdXNzaWEnICYgY291bnRyaWVzX3NwZGYkcmVnaW9uPT0nRXVyb3BlJyxdDQoNCiMgQ2FsY3VsYXIgZWwgw6FyZWEgZGUgY2FkYSBwYcOtcyBlbiBrbV4yDQpFdXJvcGVfd29fcnVzc2lhJGFyZWFfa20yIDwtIGFyZWEoRXVyb3BlX3dvX3J1c3NpYSkvMTBeNg0KDQojIENhbGN1bGFyIGxhIGRlbnNpZGFkIGRlIHBvYmxhY2nDs24NCkV1cm9wZV93b19ydXNzaWEkZGVuc2l0eSA8LSBFdXJvcGVfd29fcnVzc2lhJHBvcHVsYXRpb24vRXVyb3BlX3dvX3J1c3NpYSRhcmVhX2ttMg0KDQojIENyZWFyIHVuIG1hcGEgY29uIGxhIGRlbnNpZGFkIGRlIHBvYmxhY2nDs24gY29tbyB2YXJpYWJsZSBkZSBjb2xvcg0KdG1fc2hhcGUoRXVyb3BlX3dvX3J1c3NpYSkgKw0KICB0bV9maWxsKGNvbCA9ICJkZW5zaXR5IikgKw0KICB0bV90ZXh0KCJpc29fYTMiLCBzaXplID0gImFyZWFfa20yIikgKw0KICB0bV9sYXlvdXQoZnJhbWUgPSBGQUxTRSkNCmBgYA0KDQo3LiBPYnNlcnZhIGVsIGVmZWN0byBkZWwgcGFyw6FtZXRybyAic3R5bGUiIGVuICoqdG1fZmlsbCoqLiBQcnVlYmEgKipzdHlsZT0icXVhbnRpbGUiKioNCg0KYGBge3J9DQp0bV9zaGFwZShFdXJvcGVfd29fcnVzc2lhKSArDQogIHRtX2ZpbGwoY29sID0gImRlbnNpdHkiLCBzdHlsZSA9ICJxdWFudGlsZSIpICsNCiAgdG1fdGV4dCgiaXNvX2EzIiwgc2l6ZSA9ICJhcmVhX2ttMiIpICsNCiAgdG1fbGF5b3V0KGZyYW1lID0gRkFMU0UpDQpgYGANCg0KOC4gR3VhcmRhIGVsIG1hcGEgZW4gbW9kbyBlc3TDoXRpY28uDQoNCmBgYHtyfQ0KdG1hcF9zYXZlKGZpbGVuYW1lID0gJ0V1cm9wYV9zaW5fUnVzaWEucG5nJykNCmBgYA0KDQo=